/**
	License:		GNU General Public License version 3 (see license.txt, also available online at http://www.gnu.org/licenses/gpl-3.0.html)
	Authors:		OrbitalLab (http://www.orbitallab.ru/dee0xd/), 2008-2009

	File:			dmd.d
	Description:	some dmd stuff
	Date:			09.12.2008 by Digited
**/
module dee0xd.compilers.DMD;

import
	tango.text.Unicode,

	dee0xd.Util,
	dee0xd.Log,
	dee0xd.compilers.ICompiler;

//---------------------------------------------------------------------------------------------------
/// DMD compiler class
//---------------------------------------------------------------------------------------------------
class DMD : ICompiler
{
	protected:
		char _sep;

		this()
		{
			cmdRename	= [dxd_internal_rename];

			version( Windows )
			{
				cmdCompiler	= ["dmd"];
				cmdLinkExe	= ["dmd"];
				cmdLinkLib	= ["lib", "-c"];
				cmdExe		= ".exe";
				cmdObj		= ".obj";
				cmdLib		= ".lib";

				_sep = '\\';
			}
			else
			{
				cmdCompiler	= ["dmd"];
				cmdLinkExe	= ["gcc"];
				cmdLinkLib	= ["ar", "rcs"];
				cmdExe		= "";
				cmdObj		= ".o";
				cmdLib		= ".a";

				_sep = '/';
			}
		}

		static DMD _singleton;

	public:
		static DMD opCall()
		{
			if( _singleton is null )
				_singleton = new DMD;

			return _singleton;
		}


		char[][][] GenCommands( char[][char[]] params, FilePath[] modules )
		{
			char[][][] result;

			/*	logic:

					do bindir;
					command = compiler + flags + versions + includes

					if( !nolink )
						linkCommand = -of + libpaths + libcommands
					
					if( !(!group || ( group && stlib && !seplink ))		// we don't need sets if !group or if group+lib+!seplink; in all other ways we do
					{
						GroupModulesToSets();
						multiset = sets.length > 1
					}

					enum EModuleGrouping { all, sets, solo };
					enum EModuleOverwriteAvoidingMethod { separate_folders (fast), rename_all (slow) }

					Definition:
						all:	group !seplink with lib or !multiset
						sets:	group but !all; can be nolink, can be group with !multiset but seplink - no matter the sets count
						solo:	!group
					
					Scenario:
						all:
							if( !lib )
								outname = -of<path>;
								
							command += outname;
							
							foreach module
								command += bindir + module;
								
							if( !nolink )
								command += linkCommand;

						sets:
							bool renaming = rename_all && multiset	// we have to rename all modules, if the user wants to and we have sets.number > 1
							
							foreach set
							{
								outdir = -od<bindir>
								
								if( set index > 0 )
									outdir += toString!(int)index
									
								renameCmds.clear;
								commands = command;
								
								foreach module
								{
									commands += module
									outname = outdir;
									
									if( renaming )
									{
										do modifiedName;
										renameCmds += do renameCmd with modifiedName;
										outname += modifiedName;
									}
									else
										outname += module.name;
									
									objs += outname
								}
								
								result += commands;
								
								if( renaming )
									result += renameCmds;
							}
							
							if( !nolink )
								result += [linker, do outname, objs, linkCommand];
							
						solo:
							foreach module
							{
								outname = -of<bindir> + modifiedName;
								commands += [command, module, outname];
								objs += outname;								
							}
							
							if( !nolink )
								result += [linker, do outname, objs, linkCommand];							
			*/

			//**************** Stage 1 :: preparing **************************
			bool
				stlib	    = ( "lib" in params ) !is null,
				seplink		= ( "seplink" in params ) !is null,
				nolink		= ( "nolink" in params ) !is null,
				link		= !nolink,
				group		= ( "group" in params ) !is null,
				check		= ( "check" in params ) !is null,
				placedInExisting,
				multiset,
				superlib    = group && stlib && !(nolink || seplink);

			char[]*
				path		= "path" in params,
				fileName	= "name" in params,
				outDir		= "outdir" in params,
				binDir		= "bindir" in params,
				includes	= "-I" in params,
				libs		= "-l" in params,
				libPaths	= "-L" in params,
				pageSize	= "-p" in params,
				dbg			= "-g" in params,
				doc			= "-d" in params,
				di			= "-h" in params,
				optimize	= "-o" in params,
				release		= "-r" in params,
				inl			= "-i" in params,
				unit_test	= "-u" in params,
				ver			= "-v" in params,
				other		= "add" in params;

			assert( path );
			uint pathLength = (*path).length + 1; // 1 for '/' while slicing FilePaths of files in *path

			assert( outDir );
			assert( fileName );

			char[]
				outDirStr = *outDir,
				binDirStr = (binDir !is null) ? *binDir : outDirStr ~ _sep ~ "bin" ~ _sep ~ *fileName, // must exist!
				objPath,
				outName,
				outputDir,
				tmpLastName, tmpNewName;

			FilePath[char[]][]	sets;
			
			char[][]
				objs,
				command = cmdCompiler,
				compilingCmds,
				linkingCmds,
				renCmd,
				outNameCmd;
			
			char[][][] renameCmds;

			if( dbg )		compilingCmds ~= "-g";
			if( doc )		compilingCmds ~= "-D";
			if( di )		compilingCmds ~= "-H";
			if( optimize )	compilingCmds ~= "-O";
			if( inl )		compilingCmds ~= "-inline";
			if( release )	compilingCmds ~= "-release";
			if( unit_test )	compilingCmds ~= "-unittest";

			if( ver )
				foreach( v; split( *ver, ";" ))
					if( v.length )
						compilingCmds ~= "-version=" ~ v;

			if( other )
				foreach( flag; split( *other, ";" ))
					if( flag.length )
						compilingCmds ~= flag;

			if( includes )
				foreach( inc; split( *includes, " " ))
					if( inc.length )
						compilingCmds ~= inc;

			command ~= compilingCmds;

			//************* Stage 2 :: do sets if we have to *****************

			if( !(!group || superlib ))		// we don't need to generate sets if !group or if group+lib+!seplink; in all other ways we do
			{
				char[] lowerCaseName;

				foreach( md; modules )
				{
					lowerCaseName = toLower( md.name );
					placedInExisting = false;

					foreach( ref set; sets )
						if(( lowerCaseName in set ) is null )
						{
							set[lowerCaseName] = md;
							placedInExisting = true;
							break;
						}

					if( !placedInExisting )
					{
						FilePath[char[]] newSet;
						newSet[lowerCaseName] = md;
						sets ~= newSet;
					}
				}

				multiset = sets.length > 1;
			}

			if( !( nolink || stlib ) && libs )		// if we'll do linking to executable and we have libs to link
			{
				if( libPaths )
					version( Posix )
					{
						foreach( libPath; split( *libPaths, ";" ))
							if( libPath.length )
								linkingCmds ~= ((seplink || multiset) ? "-L" : "-L-L") ~ libPath;
					}
					else
						version( Windows )
							FixLibPaths( *libPaths, *libs, *path );

				foreach( lib; split( *libs, " " ))
					if( lib.length )
						version( Posix )	linkingCmds ~= ((seplink || multiset) ? "-l" : "-L-l") ~ lib;
						else				linkingCmds ~= lib;
			}

			//*************** Stage 3 :: generating commands, depending on mode *******************
			
			if( group && !seplink && ( stlib || !multiset ))	//**************** alltogether mode ***************************
			{
				log.trace( "compiling modules alltogether..." );

				outName = ((outDirStr.length) > 0 ? (outDirStr ~ _sep ~ *fileName) : *fileName) ~ ( stlib ? cmdLib : cmdExe );
				
				if( check )
				{
					// get modify date of output file, if exists, and compare with dates of modules:
					// if there are any modules older that output, switch to sets mode
				}

				if( stlib )
					command ~= "-lib";
				else
					if( outDirStr.length )
						command ~= "-od" ~ outDirStr;

				command ~= "-of" ~ outName;

				foreach( md; modules )
					command ~= md.toString[pathLength..$];

				if( link )
					command ~= linkingCmds;
					
				result ~= command;
			}
			else
			{
				command ~= "-c";

				if( group )		//************************** sets mode *********************************
				{
					log.trace( "compiling modules in sets..." );
					bool renaming = (("--rename" in params) !is null) && multiset;

					foreach( set_index, set; sets )
					{
						renameCmds.length = 0;
						compilingCmds = command;

						if( binDirStr.length > 0 )
						{
							outputDir = binDirStr;

							if( !renaming && set_index > 0 )
								outputDir ~= _sep ~ tango.text.convert.Integer.toString( set_index );
							
							compilingCmds ~= "-od" ~ outputDir;
						}
						
						foreach( md; set )
						{
							compilingCmds ~= md.toString[pathLength..$];
							
							if( link )
							{
								objPath.length = 0;

								if( outputDir.length )
									objPath ~= outputDir ~ _sep;
							}

							if( renaming )
							{
								tmpLastName = objPath ~ md.name ~ cmdObj;
								tmpNewName = objPath ~ replace( md.toString[pathLength..$-2].dup, '/', '_' ) ~ cmdObj;
								
								if( tmpNewName != tmpLastName )
								{
									renCmd = cmdRename;
									renCmd ~= tmpLastName;
									renCmd ~= tmpNewName;

									renameCmds ~= renCmd;
								}

								if( link )
									objs ~= tmpNewName;
							}
							else
								if( link )
									objs ~= objPath ~ md.name ~ cmdObj;
						}
						
						result ~= compilingCmds;
						
						if( renaming )
							result ~= renameCmds;
					}
				}
				else	//********************** solo mode *****************************
				{
					foreach( md; modules )
					{
						objPath = "";

						if( binDirStr.length )
							objPath ~= binDirStr ~ _sep;

						outName = md.toString[pathLength..$];
						objPath ~= replace( outName[0..$-2].dup, '/', '_' ) ~ cmdObj;

						result ~= command ~ [outName, "-of" ~ objPath];
						objs ~= objPath;								
					}
				}
				
				if( link )
				{
					outNameCmd.length = 0;
					outName.length = 0;
					
					version( Windows )
						if( pageSize && !superlib && stlib )
							outNameCmd ~= "-p" ~ *pageSize;

					if( !stlib )
						version( Posix )
							outNameCmd ~= "-o";

					version( Windows )
						if( !seplink && !stlib )
							outName ~= "-of";

					if( outDirStr.length )
						outName ~= outDirStr ~ _sep;

					outName ~= *fileName ~ ( stlib ? cmdLib : cmdExe );
					outNameCmd ~= outName;

					if( !stlib && dbg )
						outNameCmd ~= "-g";

					linkingCmds = (stlib ? cmdLinkLib : cmdLinkExe ) ~ outNameCmd ~ objs ~ linkingCmds;
					result ~= linkingCmds;
				}
			}

			return result;
		}

		//---------------------------------------------------------------------------------------------------
		/// utility function for dmd and windows: looks for import libs in given directories, if finds - prepends dir to lib (dmd & win way to "add" user lib directories)
		//---------------------------------------------------------------------------------------------------
		version( Windows ) void FixLibPaths( char[] libPaths, ref char[] libs, char[] startPath )
		{
			char[][char[]] libMap;

			foreach( lib; split( libs, " " ))
				if( lib.length )
					libMap[lib.dup] = lib.dup;

			foreach( libPath; split( libPaths, ";" ))
				if( libPath.length )
				{
					foreach( lib; SearchFiles( libPath, startPath, cmdLib, false ))
					{
						char[] libName = lib.file;

						if( libName in libMap )
						{
							if( libPath[$-1] == _sep )
								libMap[libName] = libPath ~ libName;
							else
								libMap[libName] = libPath ~ _sep ~ libName;

							log.trace( "linking [" ~ libMap[libName] ~ "]" );
						}
					}
				}

			libs = "";

			foreach( lib; libMap )
				libs ~= lib ~ " ";
		}
}